// CDN_NET_TCP_Acceptor.h
//
// Copyright (c) 2011 LRUnit, LLC All Rights Reserved.
// Author : Kim McKinley
//
// This product includes software developed by :
//  LRUnit, LLC
//
// This software is under the terms of the MIT License :
/*
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
*/

#if !defined(CDN_NET_TCP_Acceptor_h)
#define CDN_NET_TCP_Acceptor_h

namespace cdn {
  namespace net {
    namespace tcp {

// **********************************************************************
/// TCP/IP Acceptor
/**
  Performs all the TcpIp network init, socket, binding, listening, accepting 
  network magic to get data from the network.  Data is directly loaded into
   a web server handler for parsing and processing.
  Multiple threads of the Acceptor wait on accept() from the network. This
   insures that concurrent requests are processed in a timely manner.\n
  These threads waiting on accept() are considered 'Idle'.  Once they accept()
   input from the network another thread is spawned to replace it.
*/
// Derived from mutex to provide thread safe class variables
class Acceptor : os::Mutex
{
public:

  // Listening on port and socket
  int ServerPort_;          ///< Listening port
  SOCKET ListenSocket_;     ///< Listening socket

  int MaxWaitThreads_;          ///< Max number of threads waiting on accept()
  int WaitingThreads_;      ///< Current number of waiting threads

  bool ShutdownComplete_;   ///< Set true after graceful shutdown

  /// Map of currently active web server threads (by client socket)
  std::map< SOCKET, cdn::web::srv::Handler* > ActiveMap_;

  /// Constructor sets up the port and max threads that will be waiting on accept()
  Acceptor()
  {
    ServerPort_ = atoi(gConfig["DEFAULT"]["SERVER_PORT"].c_str());
    ListenSocket_ = SOCKET_ERROR;
    MaxWaitThreads_ = 30;
    WaitingThreads_ = 0;
    ShutdownComplete_ = false;
  }

  /// os::Thread calls here to process TcpIP requests
  static void Start( void* pMyInstance )
  {
    // Get pointer to ourself - @todo - should probably do a cast
    ((Acceptor *)pMyInstance)->Listener();
  }

  // Call here to shutdown accepting requests
  void Stop()
  {
    // Closing the listening socket will initiate the shutdown
    closesocket(ListenSocket_);

    // Wait for the shutdown to complete
    while ( !ShutdownComplete_) Sleep(1000);
  }

// **********************************************************************
  /// Accepts HTTP requests
  void Listener()
  {
    // Initialize the winsock library
    WSADATA wsaData;
    WSAStartup(0x101, &wsaData);

    // Lock so that the WaitingThreads_ counter stays sane
    //  also - the lock insures that only a single thread is
    //  used to setup the listen socket
    lock();

    // The initial thread runs the listen socket setup - create/bind/listen
    if (ListenSocket_ == SOCKET_ERROR)
      if ( !SetupListenSocket() ) return;

    // Check to see if we are running at the max number of threads allowed to wait on accept()s
    // If not maxed then spawn one more thread - (which would spawn one more, which would spawn
    //  one more, etc) until we reach the max allowed number of waiting threads
    if ( WaitingThreads_ < MaxWaitThreads_ )
    {
      WaitingThreads_++;
      os::Thread(Start, this);
    }

    // Listen socket setup is complete - (or was already setup) so can now unlock
    unlock();

    // Initialize a web server handler
    web::srv::Handler webServer;

    // Accept the client connection
    if ((webServer.clientSocket_ = accept(ListenSocket_,(struct sockaddr *)&webServer.clientAddress_,
          &webServer.clientAddressLen_)) == SOCKET_ERROR)
      {
        // Ut-oh - fail to accept socket library in trouble (or listen socket was closed)
        ShutdownWaitingThread();
        return;
      }

    // Since this thread accepted a connection, it is now committed 
    //  Fire up another acceptor thread to replace it - to wait on accept
    os::Thread(Start, this);

    // Startup web server event handler thread to do a timeout if requestor doesn't disconnect
    os::Thread(webServer.StartEventHandler, &webServer);

    // Put this server on the active thread map so we can keep track of it
    lock(); ActiveMap_[webServer.clientSocket_] = &webServer; unlock();

    // This waiting thread now has become the thread that will run this active server,
    //   until death does it part

    // Keep getting data until someone closes the socket
    while ( true )
    {
      //.We are only going to wait so long for data
      webServer.ResetTimer(10000);
      // Have a need for speed! Receive data directly into the handler instance
      webServer.datasize = recv(webServer.clientSocket_, webServer.data, MAX_WEB_RECV_LEN, 0);

      // Got data - so call web server handler to process it
      if ( webServer.datasize > 0 )
      {
        webServer.ProcessRequest();
        continue;  // Done with that request - wait for another over this same connection
      }
      else // Have issues so deal with the error
      {
        ClientSocketError(webServer.datasize);
        break;
      }
    }

    // We are done with the web server
    webServer.SignalShutdown();

    // Remove server from the active list of web server instances
    lock();
    ActiveMap_.erase(webServer.clientSocket_);
    #ifdef _DEBUG
      if ( ActiveMap_.size() == 0 ) std::cout << std::endl;
    #endif
    unlock();

    // This thread is outta here
    WSACleanup();
  }


// **********************************************************************
// Helpers

  /// Listen socket setup - create/bind/listen
  bool SetupListenSocket()
  {
    // Standard TCP socket
    if ( (ListenSocket_ = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) == SOCKET_ERROR)
    {
      std::cout << "Unable to create listen socket on port " << ServerPort_ << 
        " - Error : " << WSAGetLastError() << std::endl;
      WSACleanup();
      return false;
    }

    // Bind info
    struct sockaddr_in ourAddr;    // Our address for the bind
    ourAddr.sin_family = AF_INET;
    ourAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    ourAddr.sin_port = htons(ServerPort_);

    // Bind the listen socket
    if (bind(ListenSocket_, (struct sockaddr *)&ourAddr,sizeof(struct sockaddr_in)) == SOCKET_ERROR)
    {
      std::cout << "Unable to bind listen socket on port " << ServerPort_ << 
        " - Error : " << WSAGetLastError() << std::endl;
      closesocket(ListenSocket_);
      WSACleanup();
      ListenSocket_ = SOCKET_ERROR;
      return false;
    }

    // Listen on the port
    if (listen(ListenSocket_,10) == SOCKET_ERROR)
    {
      std::cout << "Unable to listen on port " << ServerPort_ << 
        " - Error : " << WSAGetLastError() << std::endl;
      closesocket(ListenSocket_);
      WSACleanup();
      return false;
    }

    // Let user know that we are now listening on the port
    std::cout << "------- Tcpip Connection Acceptor Started ---------" << std::endl <<
      "------- Server listening on port " << std::left << std::setw(9) << 
      ServerPort_ << "---------" << std::endl;

    return true;
  }

  /// Got an error on the client socket receive
  void ClientSocketError(int error)
  {
#ifdef _DEBUG
    // Normally, we timed out waiting for data (winsock error 10053)
    if( error == SOCKET_ERROR  )
    {
      if ( WSAGetLastError() == 10053 )
        std::cout << "T.";
      else
      {
        // std::cout << "Client socket closed - Error : " << WSAGetLastError() << std::endl;
        std::cout << "E.";
      }
    }
    // The client (browser) disconnected from us
    else
    {
        // std::cout << "Client browser disconnected - Error : " << WSAGetLastError() << std::endl;
        std::cout << "D."; // Indicates a client disconnected
    }
#endif
  }

  /// Will end up here when shutting down - or if sockets library is having a bad day!
  void ShutdownWaitingThread()
  {
    WSACleanup();

    lock();
    ListenSocket_ = SOCKET_ERROR; // The listen socket is toast - make it so

    // Let all the waiting threads exit - except the last one
    if ( --WaitingThreads_ != 0)
    {
      unlock();
      return;
    }
    unlock();

    // The last waiting thread running informs console user 
    //  that we are no longer accepting requests
    std::cout << "------- Tcpip Connection Acceptor Stopped ---------" << std::endl << 
      "------- No new requests will be accepted. ---------" << std::endl;

    // Wait for any active (web server) threads to exit
    WaitOnActiveThreads();

    // That's it - this Acceptor is shutdown
    ShutdownComplete_ = true;
    return;
  }

  /// Attempt a graceful shutdown by letting active requests to finish
  void WaitOnActiveThreads()
  {
    int SecondsRemaining = atoi(gConfig["DEFAULT"]["SECONDS_TO_WAIT_ON_SHUTDOWN"].c_str());

    // Give active requests a chance to finish
    lock();
    while ( ActiveMap_.size() > 0 )
    {
      {
        // We have waited long enough - we are done regardless
        if ( --SecondsRemaining <= 0 ) break;

        // Show we are still alive by displaying dots every second
        unlock();
        std::cout << '.'; Sleep(1000);
        lock();
      }
    }
    unlock();
  }

  
}; // class Acceptor

}  //namespace tcp
}  //namespace net
}  //namespace cdn


#endif
